package edu.cs421.unitproject;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;

public class Lexer {

	public int line = 1;
	private char peek = ' ';
	private final static String BLANK_QUOTES = "";
	private final static String POSITIVE_SIGN = "+";
	private final static String NEGATIVE_SIGN = "-";
	
	//private LinkedHashMap <Token,String> words = new LinkedHashMap <Token,String> (200,5);
	private ArrayList <Token>words = new ArrayList<Token>();
	protected static LinkedHashMap <String,KeyWord> keywords = new LinkedHashMap <String,KeyWord> (50,2);
	Scanner scanner = null;
	
	
	public Lexer(){
		ReserveWords.loadReservedTokens();
	}
	
	public List <Token> getGeneratedTokens(){
		return words;
	}

	public void parseNumber(String strSign){
		Num n = null;		
		
		if (String.valueOf(peek).matches("0|[1-9]")){
			
			String strNumber = strSign;
			do {
				strNumber = strNumber + String.valueOf(peek);
				peek = scanner.next().charAt(0);
				if (peek == 0 )  break;
			//	System.out.println(" NUmber is " + strNumber);
			}
			while (String.valueOf(peek).matches("0[x|X]?|[0-9]*|e?|E?|\\+?|-?|[a-f]*|x*|[A-F]*|\\.?|[0-9]*|[f|F|l|L|d|D]?"));
			
			System.out.println(" Number val is "+strNumber);
			
			if (strNumber.matches(RegExPattern.NONZERODIGIT)) { n = new Num(Tag.NONZERODIGIT,strNumber);}
			else if (strNumber.matches(RegExPattern.OCTALDIGIT)) { n = new Num(Tag.OCTALDIGIT,strNumber);}
			else if (strNumber.matches(RegExPattern.OCTALDIGITS)) { n = new Num(Tag.OCTALDIGITS,strNumber);}
			else if (strNumber.matches(RegExPattern.SIGNEDOCTALS)) { n = new Num(Tag.SIGNEDOCTALS,strNumber);}
			else if (strNumber.matches(RegExPattern.DIGIT)) { n = new Num(Tag.DIGIT,strNumber);}
			else if (strNumber.matches(RegExPattern.DIGITS)) { n = new Num(Tag.DIGITS,strNumber);}
			else if (strNumber.matches(RegExPattern.SIGNEDINTEGER)) { n = new Num(Tag.SIGNEDINTEGER,strNumber);}
			else if (strNumber.matches(RegExPattern.HEXDIGITS)) { n = new Num(Tag.HEXDIGITS,strNumber);}
			else if (strNumber.matches(RegExPattern.SIGNEDHEXDIGIT)) { n = new Num(Tag.SIGNEDHEXDIGIT,strNumber);}
			else if (strNumber.matches("[[\\+|-]?\\d*\\.\\d]")) { n = new Num(Tag.DOUBLENUMBER,strNumber);}
			else if (strNumber.matches(RegExPattern.LONGINTEGER)) { n = new Num(Tag.LONGINTEGER,strNumber);}
			//else if (strNumber.matches("\\d*\\.\\d+[e|E][\\+|-]?\\d*")) { n = new Num(Tag.DOUBLENUMBER,strNumber);}
			else if (strNumber.matches("[[\\+|-]?\\d+\\.\\d]|[\\d+\\.\\d+[e|E][\\+|-]\\d*]+f")) { n = new Num(Tag.FLOATNUMBER,strNumber);}
			else if (strNumber.matches("[[\\+|-]?\\d+\\.\\d]|[\\d+\\.\\d+[e|E][\\+|-]\\d*]+[d|D]?")) { n = new Num(Tag.DOUBLENUMBER,strNumber);}
			else words.add(new Token(Tag.ERROR, "Error message"));
			words.add(n);
		}
	}
	
	public void scanFile(){
		try{
			scanner = new Scanner(new BufferedReader(new FileReader("/Users/joekumar/Documents/temp/HelloAndroid.java")));
		}
		catch(Exception exp){
			exp.printStackTrace();
		}
	}
	
	public void scanString(String strInput){
		scanner = new Scanner(strInput);
	}
	
	public void parseString(){
		char prevval;
		if (peek == '"'){
			StringBuffer sbVal = new StringBuffer();
		//	System.out.println(" String char " +peek);
			do{
				sbVal.append(peek);
				prevval = peek;
				peek = scanner.next().charAt(0);
				if (prevval == '\\' && String.valueOf(peek).matches("\\\\|\"")){
						sbVal.deleteCharAt(sbVal.length() -1);	
				}
				if (peek == '\n'){
					words.add(new Token(Tag.ERROR, "Missing closing quotes. "));
					break;
				}
				
		//		System.out.println(" String char in " +peek+ " prev val "+prevval);
			}
			//while (!String.valueOf(peek).matches("\""));
			while (peek != '"' || prevval == '\\');
			System.out.println(" String val is "+ sbVal.append('"').toString());
			words.add(new Token(Tag.STRING, sbVal.toString()));
		}
	}
	
	public void parseEscSeq(){
		switch(peek){	
		case '\\' : try {String strNextVal = new String();
		//System.out.println(" inside \\ --> "+ peek);
		strNextVal = scanner.findWithinHorizon("b|t|n|f|r|\"|'|[0-7]|\\\\", 1);
		if (strNextVal.equals("b")) {  words.add(new Token(Tag.BS, "[ BS ]")); break; }
		else if (strNextVal.equals("t")) {  words.add(new Token(Tag.HT, "[ TAB ]")); break; }
		else if (strNextVal.equals("n")) {  words.add(new Token(Tag.LF, "[ LF ]")); break; }
		else if (strNextVal.equals("f")) {  words.add(new Token(Tag.FF, "[ FF ]")); break; }
		else if (strNextVal.equals("r")) {  words.add(new Token(Tag.CR, "[ RETURN ]")); break; }
		else if (strNextVal.equals("\"")) {  words.add(new Token(Tag.DOUBLEQUOTE, "[ DOUBLEQUOTE ]")); break; }
		else if (strNextVal.equals("'")) {  words.add(new Token(Tag.SINGLEQUOTE, "[ SINGLEQUOTE ]")); break; }
		else if (strNextVal.equals("\\")) {  words.add(new Token(Tag.BACKSLASH, "[ BACKSLASH ]")); break; }
		}
		catch (Exception exp){
				scanner.skip(".?.?"); // skip the 2 characters as \ is followed by some unacceptable esc sequence.
				words.add(new Token(Tag.ERROR, "Error with escape sequence")); 
			}
		}
		}	
	
	public void parseCharacter(){
		String strNextVal = new String();
	switch(peek){	
	case '\'' : try {
		strNextVal = scanner.findWithinHorizon("\\", 1);
		peek = scanner.next().charAt(0);
		//System.out.println(" peek aove is " + peek + " next val above " + strNextVal);
			
		if (String.valueOf(peek).matches("b|t|n|f|r|\"|'|[0-7]|\\\\")){
			strNextVal = String.valueOf(peek); 
			peek = scanner.next().charAt(0);
		if (peek != '\''){
				words.add(new Token(Tag.ERROR, "Error closing quotes in char literal "));	
			}else {
				if (strNextVal.equals("b")) {  words.add(new Token(Tag.CHARACTER, "[ BS ]")); break; }
				else if (strNextVal.equals("t")) {  words.add(new Token(Tag.CHARACTER, "[ TAB ]")); break; }
				else if (strNextVal.equals("n")) {  words.add(new Token(Tag.CHARACTER, "[ LF ]")); break; }
				else if (strNextVal.equals("f")) {  words.add(new Token(Tag.CHARACTER, "[ FF ]")); break; }
				else if (strNextVal.equals("r")) {  words.add(new Token(Tag.CHARACTER, "[ RETURN ]")); break; }
				else if (strNextVal.equals("\"")) {  words.add(new Token(Tag.CHARACTER, "[ DOUBLEQUOTE ]")); break; }
				else if (strNextVal.equals("'")) {  words.add(new Token(Tag.CHARACTER, "[ SINGLEQUOTE ]")); break; }
				else if (strNextVal.equals("\\")) {  words.add(new Token(Tag.CHARACTER, "[ BACKSLASH ]")); break; }
				}
			}
	}
	catch (Exception exp){
		peek = scanner.next().charAt(0);
	System.out.println(" peek is " +peek);
			if (peek == '\''){
				words.add(new Token(Tag.ERROR, "Illegal character literal quote"));
				char currChar = peek;
				peek = scanner.next().charAt(0);
				if (peek != '\''){
					peek = currChar;
					break;	
				}
			}
			else {
				strNextVal = String.valueOf(peek);
				peek = scanner.next().charAt(0);
				//System.out.println(" peekl in exp "+ peek);
				if (peek != '\''){
					words.add(new Token(Tag.ERROR, "Error closing quotes in char literal"));	
				}else {
					words.add(new Token(Tag.CHARACTER, "'"+strNextVal+"'"));
				}
			}
		}
		}
	}
	
	public Token scan(){

		try{
			
			scanner.useDelimiter("");

			while(scanner.hasNext())
			{
				peek = scanner.next().charAt(0) ;
				
				parseNumber(BLANK_QUOTES);
				
				parseString();
				
				parseEscSeq();
				
				parseCharacter();
				
				if(Character.isJavaIdentifierStart(peek)){
					StringBuffer sbVal = new StringBuffer();
					//System.out.println(" peek is a character " +peek);
					do{
						sbVal.append(peek);
						peek = scanner.next().charAt(0);
					}
					//while (!Character.isWhitespace(peek) && peek != ' ' && !Character.isSpaceChar(peek) && peek != ';' && peek != '/' && peek != '*'&& peek != '('&& peek != '['&& peek != '.');
					while(Character.isJavaIdentifierPart(peek));
					String s = sbVal.toString();
					System.out.println(" String from peek is " + s);
					
					char [] ident = s.toCharArray();
					for (int i = 0; i < ident.length; i++){
						System.out.println(" chra at " +i+"is "+ident[i]);
						if (!Character.isJavaIdentifierPart(ident[i])){
							words.add(new Token(Tag.ERROR, "Illegal literal"));
							break;
						}
					}
					
					
					Word w = (KeyWord)keywords.get(s);

					if(w != null){
						words.add((KeyWord)w);
					}
					else {
						if (s.equals("null")){
							w = new Word(Tag.NULL,s);
						}else {
							w = new Word(Tag.ID,s);
						}
						//System.out.println(" word inside else is " + w.lexeme);
						words.add(w);
					}

				}

				switch (peek){
				
				case '=' : try {String strNextVal = scanner.findWithinHorizon("=", 1); 
				if (strNextVal.equals("=")) {  words.add(new Token(Tag.EQ, "==")); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.ASSIGN, "=")); break;
				}

				case '*' : try {String strNextVal = scanner.findWithinHorizon("=|/", 1);
				if (strNextVal.equals("=")) {  words.add(new Token(Tag.SASSIGN, "*=")); break; }
				if (strNextVal.equals("/")) {  words.add(new Token(Tag.ERROR, "Error due to no starting comments")); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.TIMES, "*")); break;
				}

				case '/' : try {String strNextVal = scanner.findWithinHorizon("=|\\*|/", 1);
					if (strNextVal.equals("=")) {  words.add(new Token(Tag.DASSIGN, "/=")); break; }
					if (strNextVal.equals("/")) {  words.add(new Token(Tag.ENDOFLINECOMMENT, "//")); 
												   scanner.skip(".*");break; }
					try {
					if (strNextVal.equals("*")) {  words.add(new Token(Tag.TRADITIONALCOMMENTL, "/*"));
												   Pattern endComment = Pattern.compile(".+?\\*/",Pattern.DOTALL);
												   scanner.skip(endComment);
												   System.out.println(" matched comment "+ scanner.match());
												   words.add(new Token(Tag.TRADITIONALCOMMENTR, "*/"));
												   break; }
					} // This will catch if there are no matching end comments
					catch(Exception exp){
						//System.out.println (" comment exception "+exp);
						words.add(new Token(Tag.ERROR, "Error due to no ending comments"));
						Pattern goLast = Pattern.compile(".*",Pattern.DOTALL);
						scanner.skip(goLast);
						break;
					}
				}
				catch (Exception exp){
					words.add(new Token(Tag.DIV, "/")); break;
				}
				
				case '^' : try {String strNextVal = scanner.findWithinHorizon("=", 1);
				if (strNextVal.equals("=")) {  words.add(new Token(Tag.CASSIGN, "^=")); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.CARET, "^")); break;
				}

				case '%' : try {String strNextVal = scanner.findWithinHorizon("=", 1);
				if (strNextVal.equals("=")) {  words.add(new Token(Tag.PCASSIGN, "%=")); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.PERCENT, "%")); break;
				}
				
				case '!' : try {String strNextVal = scanner.findWithinHorizon("=", 1);
				if (strNextVal.equals("=")) {  words.add(new Token(Tag.NEQ, "!=")); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.EXCLAM, "!")); break;
				}

				case '&' : try {
					String strNextVal = scanner.findWithinHorizon("&|=", 1);
					if (strNextVal.equals("&")) {  words.add(new Token(Tag.AND, "&&")); break; }
					else if (strNextVal.equals("=")) {  words.add(new Token(Tag.AASSIGN, "&=")); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.AMP, "&")); break;
				}

				case '|' : try {String strNextVal = scanner.findWithinHorizon("\\||=", 1);
				if (strNextVal.equals("|")) {  words.add(new Token(Tag.OR, "||")); break; }
				else if (strNextVal.equals("=")) {  words.add(new Token(Tag.VASSIGN, "|=")); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.VBAR, "|")); break;
				}

				case '+' : try {String strNextVal = new String();
				System.out.println(" inside plus"+ peek);
				strNextVal = scanner.findWithinHorizon("\\d|\\+|=", 1);
				if (strNextVal.equals("+")) {  words.add(new Token(Tag.DPLUS, "++")); break; }
				else if (strNextVal.equals("=")) {  words.add(new Token(Tag.PASSIGN, "+=")); break; }
				else if (strNextVal.matches("\\d")){
					peek = strNextVal.charAt(0);
					parseNumber(POSITIVE_SIGN);break; }
				}
				catch (Exception exp){
						words.add(new Token(Tag.PLUS, "+")); break;
				}

				case '-' : try {String strNextVal = scanner.findWithinHorizon("\\d|-|=", 1);
				if (strNextVal.equals("-")) {  words.add(new Token(Tag.DMINUS, "--")); break; }
				else if (strNextVal.equals("=")) {  words.add(new Token(Tag.MASSIGN, "-=")); break; }
				else if (strNextVal.matches("\\d")){
					peek = strNextVal.charAt(0);
					parseNumber(NEGATIVE_SIGN); break; }
				}
				catch (Exception exp){
					words.add(new Token(Tag.MINUS, "-")); break;
				}

				case '>' : 
				try {
					String strNextVal = scanner.findWithinHorizon("=|>", 1);
					if (strNextVal.equals(">")) {
						try { 
							strNextVal = scanner.findWithinHorizon("=|>", 1);
							if (strNextVal.equals(">")) { 
								try { strNextVal = scanner.findWithinHorizon("=", 1);
									if (strNextVal.equals("=")) { words.add(new Token(Tag.TGTASSIGN, ">>>=")); break; } 
									}
								catch (Exception exp){
									words.add(new Token(Tag.TGT, ">>>")); break; 
									}
								} 
							else if (strNextVal.equals("=")) {  words.add(new Token(Tag.DGTASSIGN, ">>=")); break; }
						}
						catch (Exception exp){
							words.add(new Token(Tag.DGT, ">>")); break;
						}
					}
					else if (strNextVal.equals("=")) {  words.add(new Token(Tag.GEQ, ">=")); break; }
					}
				catch (Exception exp){
					words.add(new Token(Tag.GT, ">")); break;
				}

				case '<' : 
					try {
						String strNextVal = scanner.findWithinHorizon("=|<", 1);
						if (strNextVal.equals("<")) {
							try { 
								strNextVal = scanner.findWithinHorizon("=", 1);
								if (strNextVal.equals("=")) { 
										 words.add(new Token(Tag.DLTASSIGN, "<<=")); break; } 
									} 
							catch (Exception exp){
								words.add(new Token(Tag.DLT, "<<")); break;
							}
						}
						else if (strNextVal.equals("=")) {  words.add(new Token(Tag.LEQ, "<=")); break; }
						}
					catch (Exception exp){
						words.add(new Token(Tag.LT, "<")); break;
					}
				
				
				case '(' : words.add(new Token(Tag.LPAREN, "(")); break;
				case ')' : words.add(new Token(Tag.RPAREN, ")")); break;
				case '{' : words.add(new Token(Tag.LBRACE, "{")); break;
				case '}' : words.add(new Token(Tag.RBRACE, "}")); break;
				case '[' : words.add(new Token(Tag.LBRAC, "[")); break;
				case ']' : words.add(new Token(Tag.RBRAC, "]")); break;
				case ';' : words.add(new Token(Tag.SEMI, ";")); break;
				case ',' : words.add(new Token(Tag.COMMA, ",")); break;
				case '.' : try {String strNextVal = scanner.findWithinHorizon("\\d", 1);
							if (strNextVal.matches("\\d")) {  }
				}
				catch(Exception exp){
							words.add(new Token(Tag.PERIOD, ".")); break;
				}
				case '~' : words.add(new Token(Tag.TILDE, "~")); break;
				case '?' : words.add(new Token(Tag.QUEST, "?")); break;
				case ':' : words.add(new Token(Tag.COLON, ":")); break;

				}
				String strPeekVal = String.valueOf(peek);
				if (strPeekVal.matches("\\s"))
				{
					words.add(new Token(Tag.WHITESPACE, "WS")); 
				}
			}
		}

		catch(Exception exp){
			exp.printStackTrace();
		}

		finally{
			scanner.close();
		}

		for (Iterator <Token> wordlist = words.iterator(); wordlist.hasNext();)
		System.out.println(" values in Token list "+wordlist.next());
		
		
		Token t = new Token(String.valueOf(peek));
		peek = ' ';
		return t;
	}

}
